// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#pragma once

#include <functional>
#include <type_traits>

#include <fbl/macros.h>
#include <lib/async/dispatcher.h>
#include <lib/fidl-async/bind.h>
#include <lib/zx/channel.h>
#include <zircon/assert.h>
#include <zircon/fidl.h>

namespace fidl {

// A wrapper class which helps binding operations to a channel, and which
// helps creating C-compatible member-binding functions.
//
// This class ensures that the "void*" context value passed to |fidl_bind|
// is the same value used to dispatch to member functions.
template <typename T>
struct Binder {
  // Bind a member of a base class to a FIDL dispatch function, to allow
  // compatibility with the C bindings.
  //
  // For example FIDL functions:
  //
  //   1: MyFunction(Args) -> (ReturnValue);
  //   2: MyNonReturning(Args);
  //
  // The following C signatures will be generated by the FIDL compiler, and are expected in a
  // server's dispatch table that wishes to implement this interface:
  //
  //   zx_status_t MyFunction(void* ctx, Args... args, fidl_txn_t* txn);
  //   zx_status_t MyNonReturning(void* ctx, Args... args);
  //
  // This functionality can be implemented with this helper like so:
  //
  //   class MyClass {
  //   public:
  //       zx_status_t FunctionImplementation(Args... args, fidl_txn_t* txn) { ... }
  //       zx_status_t NonReturningImplementation(Args... args) { ... }
  //   };
  //
  //   fidl_MyInterface_ops_t ops = {
  //       .MyFunction = Binder<MyClass>::BindMember<&MyClass::FunctionImplementation>,
  //       .MyNonReturning = Binder<MyClass>::BindMember<&MyClass::NonReturningImplementation>,
  //   };
  //
  // Which will instantiate functions with signatures matching the auto-generated "My*Function"
  // C bindings, that automatically invoke the *FunctionImplementation member functions, using a
  // "MyClass" instance as context.
  template <auto Fn, typename... Args>
  static zx_status_t BindMember(Args... args) {
    auto memfn = std::mem_fn(Fn);
    return BindInternal(memfn, args...);
  }

  // Bind a disambiguated, overloaded member of a base class to a FIDL dispatch function, to allow
  // compatibility with the C bindings.
  //
  // For a FIDL function:
  //
  //   1: MyFunction(Args) -> (ReturnValue);
  //
  // The following C signature will be generated by the FIDL compiler, and is expected in a
  // server's dispatch table that wishes to implement this interface:
  //
  //   zx_status_t MyFunction(void* ctx, Args... args, fidl_txn_t* txn);
  //
  // This functionality can be implemented with this helper like so:
  //
  //   class MyClass {
  //   public:
  //       zx_status_t FunctionOverloaded(Args... args, fidl_txn_t* txn) { ... }
  //       zx_status_t FunctionOverloaded(OtherArgs... other_args);
  //   };
  //
  //   fidl_MyInterface_ops_t ops = {
  //       .MyFunction = Binder<MyClass>::BindMember<zx_status_t(Args..., fidl_txn_t*),
  //                                                 &MyClass::FunctionOverloaded>
  //   };
  //
  // Which will instantiate a function with a signature matching the auto-generated "MyFunction"
  // C binding, that automatically invokes the FunctionOverloaded member function matching the
  // specified signature, using a "MyClass" instance as context.
  template <typename FnSig, FnSig T::*Fn, typename... Args>
  static zx_status_t BindMember(Args... args) {
    auto memfn = std::mem_fn<FnSig, T>(Fn);
    return BindInternal(memfn, args...);
  }

  // A utility function which simplifies binding methods of derived methods to FIDL
  // dispatch functions.
  //
  // A typical use case would look like the following:
  //
  // FIDL:
  //
  //   1: MyFunction(Args) -> (ReturnValue);
  //
  // C++:
  //
  //   class MyClass {
  //   public:
  //       zx_status_t FunctionImplementation(Args... args, fidl_txn_t* txn) {
  //           ...;
  //       }
  //
  //       zx_status_t Bind(async_dispatcher_t* dispatcher, zx::channel channel) {
  //           static constexpr Interface_ops_t kOps = {
  //               .MyFunction = Binder<MyClass>::BindMember<&MyClass::FunctionImplementation>,
  //           };
  //           return Binder<MyClass>::BindOps<Dispatch>(dispatcher, channel, this, &kOps);
  //       }
  //   }
  //
  //   ...
  //
  //   MyClass instance;
  //   instance.Bind(dispatcher, channel);
  template <auto Dispatch, typename Ops>
  static zx_status_t BindOps(async_dispatcher_t* dispatcher, zx::channel channel, T* ctx,
                             const Ops* ops) {
    static_assert(std::is_same<decltype(Dispatch), zx_status_t (*)(void*, fidl_txn_t*, fidl_msg_t*,
                                                                   const Ops* ops)>::value,
                  "Invalid dispatch function");
    return fidl_bind(dispatcher, channel.release(), reinterpret_cast<fidl_dispatch_t*>(Dispatch),
                     ctx, ops);
  }

 private:
  template <typename MemFn, typename... Args>
  static zx_status_t BindInternal(MemFn memfn, void* ctx, Args... args) {
    auto instance = static_cast<T*>(ctx);
    return memfn(instance, args...);
  }
};

// Wrapper class around |fidl_async_txn_t|. This allows a transaction to be
// initialized, moved, reset, and completed without unintentionally "double completing"
// or "forgetting to complete".
class AsyncTransaction {
 public:
  DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(AsyncTransaction);

  AsyncTransaction() : txn_(nullptr) {}
  explicit AsyncTransaction(fidl_txn_t* txn) : txn_(fidl_async_txn_create(txn)) {}
  explicit AsyncTransaction(AsyncTransaction&& other) : txn_(other.release()) {}
  AsyncTransaction& operator=(AsyncTransaction&& other) {
    Reinitialize(other.release());
    return *this;
  }

  ~AsyncTransaction() { Reinitialize(); }

  // Acquires a reference to the |fidl_txn_t| backing this txn object.
  // This reference will be invalidated if any non-const operations are called
  // on |AsyncTransaction|.
  //
  // Should not be called if the underlying transaction is invalid.
  fidl_txn_t* Transaction() const {
    ZX_DEBUG_ASSERT(HasTransaction());
    return fidl_async_txn_borrow(txn_);
  }

  // Completes the transaction and rebinds the underlying channel against the binding.
  //
  // Causes the transaction to become invalid.
  //
  // Should not be called if the underlying transaction is invalid.
  zx_status_t Rebind() {
    ZX_DEBUG_ASSERT(HasTransaction());
    return fidl_async_txn_complete(release(), true);
  }

  // Completes the current transaction, if one exists, and causes |AsyncTransaction| to
  // track a new |txn|.
  //
  // If |txn| is nullptr, this function causes the underlying transaction to become invalid.
  void Reset(fidl_txn_t* txn = nullptr) {
    fidl_async_txn_t* async_txn = txn ? fidl_async_txn_create(txn) : nullptr;
    Reinitialize(async_txn);
  }

 private:
  bool HasTransaction() const { return txn_ != nullptr; }

  __attribute__((warn_unused_result)) fidl_async_txn_t* release() {
    fidl_async_txn_t* txn = txn_;
    txn_ = nullptr;
    return txn;
  }

  void Reinitialize(fidl_async_txn_t* txn = nullptr) {
    if (HasTransaction()) {
      fidl_async_txn_complete(release(), false);
    }
    txn_ = txn;
  }

  fidl_async_txn_t* txn_;
};

}  // namespace fidl
